/*
 * Copyright (c) 2007 Sun Microsystems, Inc. All rights reserved
 * Use is subject to license terms.
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#if defined(UNIT_TEST)
#include "unit_test.h"
#else

#include "apr.h"
#include "apr_strings.h"
#include "apr_thread_proc.h"
#include "apr_hash.h"
#include "apr_user.h"
#include "apr_lib.h"
#include "apr_optional.h"

#endif

#include "mod_atom.h"

typedef struct {
    int index;
    char * name;
    int ns_index;
} element_info_t;

typedef struct {
    genxWriter gw;
    genxSender * sender;
    genxElement * elements;
    genxAttribute * attributes;
    genxNamespace * namespaces;
} writer_t;

static element_info_t element_info[] = {
    { APP_ACCEPT_E, "accept", APP_NS_NUM },
    { APP_CATEGORIES_E, "categories", APP_NS_NUM },
    { APP_COLLECTION_E, "collection", APP_NS_NUM },
    { APP_COLLECTION_E, "collection", APP_NS_NUM },
    { APP_EDITED_E, "edited", APP_NS_NUM },
    { APP_SERVICE_E, "service", APP_NS_NUM },
    { APP_WORKSPACE_E, "workspace", APP_NS_NUM },

    { ATOM_AUTHOR_E, "author", ATOM_NS_NUM },
    { ATOM_CATEGORY_E, "category", ATOM_NS_NUM },
    { ATOM_CONTENT_E, "content", ATOM_NS_NUM },
    { ATOM_CONTRIBUTOR_E, "contributor", ATOM_NS_NUM },
    { ATOM_EMAIL_E, "email", ATOM_NS_NUM },
    { ATOM_ENTRY_E, "entry", ATOM_NS_NUM },
    { ATOM_FEED_E, "feed", ATOM_NS_NUM },
    { ATOM_GENERATOR_E, "generator", ATOM_NS_NUM },
    { ATOM_ICON_E, "icon", ATOM_NS_NUM },
    { ATOM_ID_E, "id", ATOM_NS_NUM },
    { ATOM_LINK_E, "link", ATOM_NS_NUM },
    { ATOM_LOGO_E, "logo", ATOM_NS_NUM },
    { ATOM_NAME_E, "name", ATOM_NS_NUM },
    { ATOM_PUBLISHED_E, "published", ATOM_NS_NUM },
    { ATOM_RIGHTS_E, "rights", ATOM_NS_NUM },
    { ATOM_SOURCE_E, "source", ATOM_NS_NUM },
    { ATOM_SUBTITLE_E, "subtitle", ATOM_NS_NUM },
    { ATOM_SUMMARY_E, "summary", ATOM_NS_NUM },
    { ATOM_TITLE_E, "title", ATOM_NS_NUM },
    { ATOM_UPDATED_E, "updated", ATOM_NS_NUM },
    { ATOM_URI_E, "uri", ATOM_NS_NUM }
};

static element_info_t attribute_info[] = {
    { META_A, "meta", MOD_ATOM_NS_NUM },
    { FIXED_A, "fixed", NO_NS },
    { HREFLANG_A, "hreflang", NO_NS },
    { HREF_A, "href", NO_NS },
    { LABEL_A, "label", NO_NS },
    { LENGTH_A, "length", NO_NS },
    { REL_A, "rel", NO_NS },
    { SCHEME_A, "scheme", NO_NS },
    { SCHEME_A, "scheme", NO_NS },
    { SRC_A, "src", NO_NS },
    { TERM_A, "term", NO_NS },
    { TITLE_A, "title", NO_NS },
    { TYPE_A, "type", NO_NS },
    { URI_A, "uri", NO_NS },
    { VERSION_A, "version", NO_NS }
};

typedef struct {
    apr_pool_t * pool;
    apr_file_t * file;
} apfile_userdata;

typedef struct {
    request_rec * r;
} apclient_userdata;

static void * apmem_alloc1(void * ud, int bytes) {
    return apr_palloc(((apfile_userdata *) ud)->pool, bytes);
}
static void * apmem_alloc2(void * ud, int bytes) {
    return apr_palloc(((apclient_userdata *) ud)->r->pool, bytes);
}

static genxStatus apclient_send(void * ud, constUtf8 s) {
    ap_rputs((char *) s, ((apclient_userdata *) ud)->r);
    return GENX_SUCCESS;
}
static genxStatus apclient_sendBounded(void * ud,
				       constUtf8 start, constUtf8 end) {
    ap_rwrite((char *) start, end - start, ((apclient_userdata *) ud)->r);
    return GENX_SUCCESS;
}
static genxStatus apclient_flush(void * ud) {
    ap_rflush(((apclient_userdata *) ud)->r);
    return GENX_SUCCESS;
}

static genxStatus apfile_send(void * ud, constUtf8 s) {
    apr_file_t * f = ((apfile_userdata *) ud)->file;
    apr_status_t status;
    apr_size_t written;
    status = apr_file_write_full(f, (void *) s, strlen((char *) s), &written);
    return (status == APR_SUCCESS) ? GENX_SUCCESS : GENX_IO_ERROR;
}
static genxStatus apfile_sendBounded(void * ud, constUtf8 start,
				     constUtf8 end) {
    apr_file_t * f = ((apfile_userdata *) ud)->file;
    apr_status_t status;
    apr_size_t written;
    status = apr_file_write_full(f, (void *) start, end - start, &written);
    return (status == APR_SUCCESS) ? GENX_SUCCESS : GENX_IO_ERROR;
}
static genxStatus apfile_flush(void * ud) {
    return GENX_SUCCESS; /* because we've been using write_full */
}

static char * declare(apr_pool_t * p, writer_t * w) {
    int i;
    genxStatus g_status;

    w->namespaces =
	(genxNamespace *) apr_palloc(p, NS_NUM_MAX * sizeof(genxNamespace));
    w->elements =
	(genxElement *) apr_palloc(p, ATOM_ELEMENT_MAX * sizeof(genxElement));
    w->attributes =
	(genxAttribute *) apr_palloc(p, ATOM_ATTRIBUTE_MAX * sizeof(genxAttribute));

    if ((!w->namespaces) || (!w->elements) || (!w->attributes))
	return "Unable to allocate memory for XML Writer";

    w->namespaces[NO_NS] = NULL;
    w->namespaces[ATOM_NS_NUM] =
	genxDeclareNamespace(w->gw, (utf8) ATOM_NS, (utf8) "a", &g_status);
    if (g_status)
	return apr_psprintf(p, "Can't declare Atom namespace: '%s'",
			    genxLastErrorMessage(w->gw));
    w->namespaces[APP_NS_NUM] =
	genxDeclareNamespace(w->gw, (utf8) APP_NS, (utf8) "p", &g_status);
    if (g_status)
	return apr_psprintf(p, "Can't declare APP namespace: '%s'",
			    genxLastErrorMessage(w->gw));
    w->namespaces[XHTML_NS_NUM] =
	genxDeclareNamespace(w->gw, (utf8) XHTML_NS, (utf8) "", &g_status);
    if (g_status)
	return apr_psprintf(p, "Can't declare XHTML namespace: '%s'",
			    genxLastErrorMessage(w->gw));

    w->namespaces[MOD_ATOM_NS_NUM] =
	genxDeclareNamespace(w->gw, (utf8) MOD_ATOM_NS, (utf8) "mod-atom",
			     &g_status);
    if (g_status)
	return apr_psprintf(p, "Can't declare mod-atom namespace: '%s'",
			    genxLastErrorMessage(w->gw));

    for (i = 0; i < ATOM_ELEMENT_MAX; i++) {
	w->elements[element_info[i].index] =
	    genxDeclareElement(w->gw, w->namespaces[element_info[i].ns_index],
			       (constUtf8) element_info[i].name, &g_status);
	if (g_status)
	    return apr_psprintf(p, "Can't declare element '%s'",
				element_info[i].name);
    }
    for (i = 0; i < ATOM_ATTRIBUTE_MAX; i++) {
	w->attributes[attribute_info[i].index] =
	    genxDeclareAttribute(w->gw, w->namespaces[attribute_info[i].ns_index],
				 (constUtf8) attribute_info[i].name,
				 &g_status);
	if (g_status)
	    return apr_psprintf(p, "Can't declare attribute '%s'",
				attribute_info[i].name);
    }

    return NULL;
}

genxStatus atom_genx_simple_elem(void * w_p, int e_num, char * content) {
    writer_t * w = (writer_t *) w_p;
    genxElement e = w->elements[e_num];
    genxWriter gw = w->gw;

    return (genxStartElement(e) ||
	    genxAddText(gw, (utf8) content) ||
	    genxEndElement(gw) ||
	    genxAddCharacter(gw, '\n'));
}

genxStatus atom_genx_nl(void * w_p) {
    return genxAddCharacter(((writer_t *) w_p)->gw, '\n');
}
genxStatus atom_genx_start_elem(void * w_p, int e_num)  {
    return genxStartElement(((writer_t *) w_p)->elements[e_num]);
}
genxStatus atom_genx_add_attr(void * w_p, int a_num, char * value)  {
    return genxAddAttribute(((writer_t *) w_p)->attributes[a_num], (utf8) value);
}
genxStatus atom_genx_add_ns(void * w_p, int ns_num) {
    return genxAddNamespace(((writer_t *) w_p)->namespaces[ns_num], NULL);
}
genxStatus atom_genx_end_elem(void * w_p) {
    return genxEndElement(((writer_t *) w_p)->gw);
}
genxSender * atom_genx_sender(void * w_p) {
    return ((writer_t *) w_p)->sender;
}

void * atom_genx_client_writer(apr_pool_t * p, request_rec * r,
			       char * * problem_p) {
    genxSender * sender;
    apclient_userdata * ud;
    writer_t * w = (writer_t *) apr_palloc(p, sizeof(writer_t));
    genxWriter gw;
    char * problem;

    ud = (apclient_userdata *) apr_palloc(p, sizeof(apclient_userdata));
    ud->r = r;

    gw = genxNew(apmem_alloc2, NULL, ud);
    if (gw == NULL)
	return "Can't create XML writer";
    w->gw = gw;

    sender = (genxSender *) apr_palloc(p, sizeof(genxSender));
    sender->send = apclient_send;
    sender->sendBounded = apclient_sendBounded;
    sender->flush = apclient_flush;
    if (genxStartDocSender(gw, sender))
	return "Can't start XML writer";
    w->sender = sender;

    if ((problem = declare(p, w))) {
	*problem_p = problem;
	return NULL;
    }

    return (void *) w;
}

genxStatus atom_genx_send(void * w_p, char * data) {
    writer_t * w = (writer_t *) w_p;
    return w->sender->send(genxGetUserData(w->gw), (utf8) data);
}

genxStatus atom_genx_end_doc(void * w_p) {
    return genxEndDocument(((writer_t *) w_p)->gw);
}

genxStatus atom_genx_add_char(void * w_p, int c) {
    return genxAddCharacter(((writer_t *) w_p)->gw, c);
}

genxWriter atom_genx_get_writer(void * w_p) {
    return ((writer_t *) w_p)->gw;
}


void * atom_genx_file_writer(apr_pool_t * p, apr_file_t * f,
			     char * * problem_p) {
    genxSender * sender;
    apfile_userdata * ud;
    genxWriter gw;
    char * problem;
    writer_t * w = (writer_t *) apr_palloc(p, sizeof(writer_t));

    ud = (apfile_userdata *) apr_palloc(p, sizeof(apfile_userdata));
    ud->pool = p;
    ud->file = f;
    gw = genxNew(apmem_alloc1, NULL, ud);
    if (gw == NULL)
	return "Can't create XML writer";

    sender = (genxSender *) apr_palloc(p, sizeof(genxSender));
    sender->send = apfile_send;
    sender->sendBounded = apfile_sendBounded;
    sender->flush = apfile_flush;
    if (genxStartDocSender(gw, sender))
	return "Can't start XML writer";

    w->gw = gw;
    w->sender = sender;
    if ((problem = declare(p, w))) {
	*problem_p = problem;
	return NULL;
    }

    return (void *) w;
}
